//  
//  Author:
//       Wouter Demuynck <wouter.am.demuynck@gmail.com>
// 
//  Copyright (c) 2011 Wouter Demuynck
// 
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//  
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// 

using System;
using System.Diagnostics;
using System.Text;
using System.IO;
using System.Configuration;
using HgSharp.Configuration;

namespace HgSharp
{
	/// <summary>
	/// Provides a wrapper arround the <b>Mercurial</b> executable.
	/// </summary>
	public static class Hg
	{
		private static string toolsPath;
		private static TimeSpan? timeout;
		
		/// <summary>
		/// Gets or sets the path to the <b>Mercurial</b> executable.
		/// </summary>
		public static string ToolsPath
		{
			get
			{
				if (toolsPath == null)
				{
					// Get the tools path from configuration.
					HgSharpSection section = ConfigurationManager.GetSection("hgSharp") as HgSharpSection;
					if (section != null)
					{
						toolsPath = section.ToolsPath;
					}
				}
				
				return toolsPath;
			}
			set
			{
				toolsPath = value;
			}
		}
		
		/// <summary>
		/// Gets or sets the time (in minutes) to wait for a <b>Mercurial</b> operation to complete before
		/// throwing an exception. The default value is 60 minutes.
		/// </summary>
		public static TimeSpan Timeout
		{
			get
			{
				if (timeout == null)
				{
					// Get the tools path from configuration.
					HgSharpSection section = ConfigurationManager.GetSection("hgSharp") as HgSharpSection;
					if (section != null)
					{
						timeout = section.Timeout;
					}
				}
				
				return timeout.Value;
			}
			set
			{
				timeout = value;
			}
		}
		
		/// <summary>
		/// Clones a <b>Mercurial</b> repository to a local working directory.
		/// </summary>
		/// <param name="uri">
		/// The <see cref="Uri"/> of the repository to clone.
		/// </param>
		/// <param name="workingDirectory">
		/// The working directory to which the repository is cloned.
		/// </param>
		/// <returns>
		/// A <see cref="Repository"/> representing the cloned repository.
		/// </returns>
		public static Repository Clone(Uri uri, string workingDirectory)
		{
			if (uri == null) throw new ArgumentNullException("uri");
			if (workingDirectory == null) throw new ArgumentNullException("workingDirectory");
			if (workingDirectory.Length == 0) throw new ArgumentException("The specified workingDirectory parameter cannot be empty.", "workingDirectory");
			
			Directory.CreateDirectory(workingDirectory);
			
			StringBuilder args = new StringBuilder();
			args.AppendFormat("clone \"{0}\" .", uri.ToString());
			
			RunHg(workingDirectory, args.ToString());
			return new Repository(workingDirectory);
		}
		
		/// <summary>
		/// Initializes a new <b>Mercurial</b> repository at the specified location.
		/// </summary>
		/// <param name="path">
		/// The location of the new repository.
		/// </param>
		/// <returns>
		/// A <see cref="Repository"/> representing the newly created repository.
		/// </returns>
		public static Repository Initialize(string path)
		{
			if (path == null) throw new ArgumentNullException("path");
			if (path.Length == 0) throw new ArgumentException("The specified path cannot be empty.", "path");
			if (!Directory.Exists(path)) Directory.CreateDirectory(path);
			
			RunHg(path, "init");
			return new Repository(path);
		}
		
		internal static void Update(string workingDirectory)
		{
			RunHg(workingDirectory, "update");
		}
		
		internal static void Commit(string workingDirectory, string message)
		{
			StringBuilder args = new StringBuilder();
			args.AppendFormat("commit -m \"{0}\" .", message);
			
			RunHg(workingDirectory, args.ToString());
		}
			
		private static void RunHg(string workingDirectory, string arguments)
		{
			EnsureToolsPath();
			
			ProcessStartInfo info = new ProcessStartInfo(Path.Combine(ToolsPath, "hg"));
			info.UseShellExecute = false;
			info.WorkingDirectory = workingDirectory;
			info.Arguments = arguments;
			info.RedirectStandardError = true;
			info.RedirectStandardInput = true;
			info.RedirectStandardOutput = true;
				
			Process hg = Process.Start(info);
			if (Timeout == TimeSpan.MaxValue)
			{
				hg.WaitForExit();
			}
			else if (!hg.WaitForExit((int)Timeout.TotalMilliseconds))
			{
				throw new TimeoutException("The Mercurial operation timed out.");
			}
		}

		private static void EnsureToolsPath()
		{
			if (string.IsNullOrEmpty(ToolsPath)) 
				throw new HgSharpException(
					"Please make sure that the ToolsPath property has been set " +
			    	"to the location of the Mercurial executable.");
			if (!Directory.Exists(ToolsPath))
				throw new HgSharpException(
					string.Format(
				    	"The Mercurial executable was not found at the specified location '{0}'.",
				        ToolsPath));
		}
	}
}